/*
 * File : $HeadURL$
 * Date : $Date: 2011-02-22 16:34:35 +0100 (Di, 22 Feb 2011) $
 * Version: $Revision: 5 $
 * 
 * This file is part of BIDS, the batch image downscaler.
 * 
 * Copyright (C) 2011 Michael Moossen
 */

package net.moossen.bids;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;

import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser;

/**
 * Entry point.
 * 
 * @author Michael Moossen
 * 
 * @version $Revision: 5 $
 */
public class DownScaler {

    /** Version string. */
    private static final String VERSION = "BIDS 1.0"; //$NON-NLS-1$

    /**
     * Entry point.
     * <p>
     * 
     * @param args command line options
     * 
     * @throws Exception if something goes wrong
     */
    public static void main(final String[] args) throws Exception {

        System.out.println(DownScaler.VERSION);
        final Options options = new Options();
        final CmdLineParser parser = new CmdLineParser(options);
        try {
            parser.parseArgument(args);
        } catch (final CmdLineException e) {
            System.err.println(e.getMessage());
            System.err.println("java -jar bids.jar [options...] [argument]"); //$NON-NLS-1$
            parser.printUsage(System.err);
            return;
        }
        if (options.isHelp()) {
            parser.printUsage(System.out);
            return;
        }
        new DownScaler(options).resize();
    }

    /** Down scaler instance. */
    private final ImageScaler m_downScaler;

    /** Command line options. */
    private final Options m_options;

    /**
     * Constructor.
     * <p>
     * 
     * @param options command line options
     */
    public DownScaler(final Options options) {

        m_options = options;
        m_downScaler = new ImageScaler("h:" + options.getHeight() + ",w:" + options.getWidth()); //$NON-NLS-1$ //$NON-NLS-2$
    }

    /**
     * Main procedure. Which iterates over all available images and resizes
     * them.
     * 
     * @throws Exception if something goes wrong
     */
    public void resize() throws Exception {

        final File outDir = m_options.getOut();
        if (!outDir.exists()) {
            outDir.mkdir();
        }
        final File dir = m_options.getIn();
        for (final File file : dir.listFiles()) {
            if (file.isDirectory()) {
                // skip dirs
                continue;
            }
            if (!file.canRead()) {
                System.err.println(file.getCanonicalPath() + ": not enough permissions");
                continue;
            }
            if ((m_options.getArgument() != null)
                && (!file.getName().matches(m_options.getArgument()))) {
                System.err.println(file.getCanonicalPath() + ": filename does not match");
                continue;
            }

            final byte[] cnt = getFileContents(file);
            try {
                final byte[] newCnt = downscale(file, cnt, m_downScaler);
                if (newCnt == null) {
                    System.err.println(file.getCanonicalPath()
                        + ": convertion not needed nor possible");
                    continue;
                }
                final File newFile = new File(outDir, file.getName());
                if (newFile.exists() && !newFile.canWrite()) {
                    System.err.println(file.getCanonicalPath() + ": not enough permissions");
                    continue;
                }
                if (newFile.exists() && !m_options.isReplace()) {
                    System.err.println(file.getCanonicalPath()
                        + ": destination file already exists (replace flag?)");
                    continue;
                }
                writeFileContents(newFile, newCnt);
                System.out.println(file.getCanonicalPath() + ": ok!");
            } catch (final Exception e) {
                System.err.println(e.getLocalizedMessage());
                System.out.println("ERROR: Skipping file: " + file.getCanonicalPath());
            }
        }
    }

    /**
     * Does the job for a given file and its content.
     * 
     * @param file the file
     * @param cnt its binary content
     * @param imageDownScaler the down scaler from the program options
     * 
     * @return the new binary content
     * 
     * @throws Exception if something goes wrong
     */
    private byte[] downscale(final File file, final byte[] cnt, final ImageScaler imageDownScaler)
    throws Exception {

        final ImageScaler scaler = new ImageScaler(cnt);
        if (!scaler.isValid()) {
            // error calculating image dimensions - this image can't be scaled
            // or resized
            return null;
        }
        // check if the image is to big and needs to be rescaled
        if (!scaler.isDownScaleRequired(imageDownScaler)) {
            return null;
        }

        byte[] result = null;

        // image is to big, perform rescale operation
        final ImageScaler downScaler = scaler.getDownScaler(imageDownScaler);
        // perform the rescale using the adjusted size
        result = downScaler.scaleImage(cnt, file.getName());
        // image size has been changed, adjust the scaler for later setting
        // of properties
        scaler.setHeight(downScaler.getHeight());
        scaler.setWidth(downScaler.getWidth());

        return result;
    }

    /**
     * Returns the binary content of the given file.
     * 
     * @param file the file to get the contents for
     * 
     * @return the binary content
     * 
     * @throws Exception if something goes wrong
     */
    private byte[] getFileContents(final File file) throws Exception {

        final FileInputStream fis = new FileInputStream(file);
        final byte[] cnt = new byte[(int)file.length()];
        final byte[] buf = new byte[1024 * 50];
        int size = 0;
        int n;
        while ((n = fis.read(buf)) != -1) {
            System.arraycopy(buf, 0, cnt, size, n);
            size += n;
        }
        fis.close();
        return cnt;
    }

    /**
     * Writes the given binary data to the given file.
     * 
     * @param file the file
     * @param cnt the data to write
     * 
     * @throws Exception if something goes wrong
     */
    private void writeFileContents(final File file, final byte[] cnt) throws Exception {

        final BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));
        bos.write(cnt);
        bos.close();
    }
}
